import math


def add_arrays(arr1, arr2):
    if len(arr1) != len(arr2):
        raise ValueError("输入数组长度必须相同")
    return [a + b for a, b in zip(arr1, arr2)]


def subtract_arrays(arr1, arr2):
    if len(arr1) != len(arr2):
        raise ValueError("输入数组长度必须相同")
    return [a - b for a, b in zip(arr1, arr2)]


def multiply_array_by_scalar(arr, scalar):
    return [x * scalar for x in arr]


class ExcelRow:

    @staticmethod
    def get_e(r1: list, b, o, p, t):
        if b > 0:
            return r1[3] + (o / 2) * (math.cos(r1[14]) + math.cos(p)) * t
        else:
            return 0

    @staticmethod
    def get_f(r1: list, b, o, p, q, t):
        if b > 0:
            return r1[4] + (o / 2) * (
                    math.sin(r1[14]) * math.cos(r1[15]) + math.sin(p) * math.cos(q)) * t
        else:
            return 0

    @staticmethod
    def get_g(r1: list, b, o, p, q, t):
        if b > 0:
            return r1[5] + (o / 2) * (
                    math.sin(r1[14]) * math.sin(r1[15]) + math.sin(p) * math.sin(q)) * t
        else:
            return 0

    @staticmethod
    def get_h(v):
        return v / math.pi * 180

    @staticmethod
    def get_i(b, f, g):
        if b > 0:
            return math.sqrt(f ** 2 + g ** 2)
        else:
            return 0

    @staticmethod
    def get_j(b, f, g, orientation, v):
        if b > 0:
            return math.cos(orientation * math.pi / 180 - v) * math.sqrt(f ** 2 + g ** 2)
        else:
            return 0

    @staticmethod
    def get_k(b, s, o):
        if b > 0:
            return s * 180 / math.pi * 30 / o
        else:
            return 0

    @staticmethod
    def get_l(b, f, ground_x):
        if b > 0:
            return ground_x + f
        else:
            return 0

    @staticmethod
    def get_m(b, g, ground_y):
        if b > 0:
            return ground_y + g
        else:
            return 0

    @staticmethod
    def get_o(r1: list, b):
        if b > 0:
            return b - r1[0]
        else:
            return 0

    @staticmethod
    def get_p(c):
        return c * math.pi / 180

    @staticmethod
    def get_q(d):
        return d * math.pi / 180

    @staticmethod
    def get_r(r1: list, p, q):
        return math.cos(p - r1[14]) - math.sin(p) * math.sin(r1[14]) * (1 - math.cos(q - r1[15]))

    @staticmethod
    def get_s(r):
        return math.acos(r)

    @staticmethod
    def get_t(s):
        try:
            return 2 / s * math.tan(s / 2)
        except (ZeroDivisionError, ValueError) as _:
            return 1

    @staticmethod
    def get_u(b, f, g):
        if b > 0:
            try:
                return abs(math.atan(g / f))
            except (ZeroDivisionError, ValueError) as _:
                return math.pi / 2
        else:
            return 0

    @staticmethod
    def get_v(f, g, u):
        if g > 0 and f > 0:
            return u
        elif g > 0 > f:
            return math.pi - u
        elif g < 0 and f < 0:
            return math.pi + u
        else:
            return 2 * math.pi - u

    @staticmethod
    def procedure_excel_row(r1: list, r2: list, orientation: float, ground_x: float, ground_y: float):
        b, c, d = r2[0], r2[1], r2[2]

        o = ExcelRow.get_o(r1, b)
        p = ExcelRow.get_p(c)
        q = ExcelRow.get_q(d)

        r = ExcelRow.get_r(r1, p, q)
        s = ExcelRow.get_s(r)
        t = ExcelRow.get_t(s)

        # ------
        e = ExcelRow.get_e(r1, b, o, p, t)
        f = ExcelRow.get_f(r1, b, o, p, q, t)
        g = ExcelRow.get_g(r1, b, o, p, q, t)

        u = ExcelRow.get_u(b, f, g)
        v = ExcelRow.get_v(f, g, u)
        h = ExcelRow.get_h(v)
        i = ExcelRow.get_i(b, f, g)
        j = ExcelRow.get_j(b, f, g, orientation, v)
        k = ExcelRow.get_k(b, s, o)
        l = ExcelRow.get_l(b, f, ground_x)
        m = ExcelRow.get_m(b, g, ground_y)

        # ------
        r2[3:] = [e, f, g, h, i, j, k, l, m, 0, o, p, q, r, s, t, u, v]


class ExcelSheet:
    # 在 Python 中，从 Python 3.10 开始，可以直接使用 X | None 替代 Optional[X]
    def __init__(self, orientation, ground_x, ground_y, rows: list = None):
        self.orientation = orientation
        self.ground_x = ground_x
        self.ground_y = ground_y
        self.base_row = [0] * 21
        self.base_row[10] = ground_x
        self.base_row[11] = ground_y
        if rows is None:
            self.rows = []
        else:
            self.rows = rows

        self.procedure_excel_sheet()

    def append_row(self, row: list):
        self.rows.append(row + [0] * (21 - len(row)))
        self.procedure_excel_sheet()

    def ensure_row_exists(self, row_index):
        while len(self.rows) <= row_index:
            if self.rows:
                new_row = [0.0] * len(self.rows[0])
            else:
                new_row = [0.0]
            self.rows.append(new_row)

    def ensure_column_exists(self, row_index, col_index):
        self.ensure_row_exists(row_index)
        while len(self.rows[row_index]) <= col_index:
            self.rows[row_index].append(0.0)

    def safe_set_value(self, row, col, value):
        self.ensure_column_exists(row, col)
        try:
            self.rows[row][col] = float(value)
        except ValueError:
            self.rows[row][col] = 0.0

    def procedure_excel_sheet(self):
        # 必须去掉 B 列（数组0列）相同的元素
        seen = set()
        self.rows = list(
            filter(lambda row1: not (row1[0] in seen or seen.add(row1[0])),
                   reversed(self.rows)))

        # 表格计算前先排序（降序）
        self.rows.sort(key=lambda x: x[0])

        # 表格计算
        old = self.base_row
        for row in self.rows:
            ExcelRow.procedure_excel_row(old, row, self.orientation, self.ground_x, self.ground_y)
            old = row

    def find_nearest_bounds(self, target: float):
        rows = self.rows
        lower_bound = None
        upper_bound = None

        for row in rows:
            if row[0] < target:
                if lower_bound is None or row[0] > lower_bound[0]:
                    lower_bound = row
            elif row[0] > target:
                if upper_bound is None or row[0] < upper_bound[0]:
                    upper_bound = row
            else:
                lower_bound = row
                upper_bound = row
        lower_bound = lower_bound if lower_bound else self.base_row
        upper_bound = upper_bound if upper_bound else self.base_row
        # if not lower_bound or not upper_bound:
        #     raise ValueError('find_nearest_bounds() 参数错误')

        return lower_bound, upper_bound

    def linear_interpolation(self, target: float) -> list:
        a, b = self.find_nearest_bounds(target)
        if b[0] - a[0] == 0:
            return a
        ratio = (target - a[0]) / (b[0] - a[0])
        # 插值计算公式 a + (b - a) * ratio
        return add_arrays(a, multiply_array_by_scalar(subtract_arrays(b, a), ratio))

    def calc_true_thickness_tilt_up_cut_down_to_tilt_up_cut_up(self, well_depth1, well_depth2, tilt_angle):
        """真厚度计算-上倾下切，上倾上切"""
        angle_rad = math.radians(tilt_angle)
        a = self.linear_interpolation(well_depth1)
        b = self.linear_interpolation(well_depth2)
        return ((b[3] - a[3]) + (b[8] - a[8]) * math.tan(angle_rad)) * math.cos(angle_rad)

    def calc_true_thickness_tilt_down_cut_down_to_tilt_down_cut_up(self, well_depth1, well_depth2, tilt_angle):
        """真厚度计算-下倾下切，下倾上切"""
        angle_rad = math.radians(tilt_angle)
        a = self.linear_interpolation(well_depth1)
        b = self.linear_interpolation(well_depth2)
        return ((b[3] - a[3]) - (b[8] - a[8]) * math.tan(angle_rad)) * math.cos(angle_rad)
